/**
 * Copyright 2016-2017 IBM All Rights Reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

'use strict';

const tape = require('tape');
const _test = require('tape-promise').default;
const test = _test(tape);

const testutil = require('./util.js');
const ecdsaKey = require('fabric-client/lib/impl/ecdsa/key.js');

const jsrsa = require('jsrsasign');
const KEYUTIL = jsrsa.KEYUTIL;
const asn1 = jsrsa.asn1;
const X509 = require('x509');

test('\n\n ** ECDSA Key Impl tests **\n\n', (t) => {
	testutil.resetDefaults();

	t.throws(
		() => {
			new ecdsaKey();
		},
		/^Error: The key parameter is required by this key class implementation, whether this instance is for the public key or private key/,
		'ECDSA Impl test: catch missing key param'
	);

	t.throws(
		() => {
			new ecdsaKey('dummy private key');
		},
		/^Error: This key implementation only supports keys generated by jsrsasign.KEYUTIL. It must have a "type" property of value "EC"/,
		'ECDSA Impl test: catch missing key type of "EC"'
	);

	t.throws(
		() => {
			new ecdsaKey({type: 'RSA'});
		},
		/^Error: This key implementation only supports keys generated by jsrsasign.KEYUTIL. It must have a "type" property of value "EC"/,
		'ECDSA Impl test: catch invalid key type'
	);

	t.throws(
		() => {
			new ecdsaKey({type: 'EC', prvKeyHex: 'some key value'});
		},
		/^Error: This key implementation only supports keys generated by jsrsasign.KEYUTIL. It must have a "pubKeyHex" property/,
		'ECDSA Impl test: catch missing "pubKeyHex" property'
	);

	t.doesNotThrow(
		() => {
			new ecdsaKey({type: 'EC', prvKeyHex: null, pubKeyHex: 'some random value'});
		},
		'ECDSA Impl test: test a valid key'
	);

	// test private keys
	const pair1 = KEYUTIL.generateKeypair('EC', 'secp256r1');
	const key1 = new ecdsaKey(pair1.prvKeyObj);
	t.equal(key1.getSKI().length, 64, 'Checking generated SKI hash string for 256 curve keys');

	t.doesNotThrow(
		() => {
			key1.toBytes();
		},
		'Checking that a private key instance allows toBytes()'
	);

	const pair2 = KEYUTIL.generateKeypair('EC', 'secp384r1');
	const key2 = new ecdsaKey(pair2.prvKeyObj);
	t.equal(key2.getSKI().length, 64, 'Checking generated SKI hash string for 384 curve keys');

	t.equal(key1.isSymmetric() || key2.isSymmetric(), false, 'Checking if key is symmetric');
	t.equal(key1.isPrivate() && key2.isPrivate(), true, 'Checking if key is private');

	t.equal(key1.getPublicKey().isPrivate(), false, 'Checking isPrivate() logic');
	t.equal(key1.getPublicKey().toBytes().length, 182, 'Checking toBytes() output');

	// test public keys
	let key3 = new ecdsaKey(pair1.pubKeyObj);
	t.equal(key3.getSKI().length, 64, 'Checking generated SKI hash string for 256 curve public key');

	t.doesNotThrow(
		() => {
			key3.toBytes();
		},
		'Checking to dump a public ECDSAKey object to bytes'
	);

	let key4 = new ecdsaKey(pair2.pubKeyObj);
	t.equal(key4.getSKI().length, 64, 'Checking generated SKI hash string for 384 curve public key');

	t.doesNotThrow(
		() => {
			key4.toBytes();
		},
		'Checking to dump a public ECDSAKey object to bytes'
	);

	t.equal(!key3.isPrivate() && !key4.isPrivate(), true, 'Checking if both keys are public');
	t.equal(key3.getPublicKey().isPrivate(), false, 'Checking getPublicKey() logic');
	t.equal(key4.getPublicKey().toBytes().length, 220, 'Checking toBytes() output');

	// test CSR generation
	const pair3 = KEYUTIL.generateKeypair('EC', 'secp256r1');
	key3 = new ecdsaKey(pair3.prvKeyObj);
	key4 = new ecdsaKey(pair3.pubKeyObj);

	t.throws(
		() => {
			key4.generateCSR('CN=publickey');
		},
		/A CSR cannot be generated from a public key/,
		'Checking that a CSR cannot be generated from a public key'
	);
	let csrPEM;
	// malformed subjectDN
	try {
		csrPEM = key3.generateCSR('###############');
		t.fail('Should not have generated a CSR with a malformed subject');
	} catch (err) {
		t.pass('Checking that CSR is not generated for a malformed subject');
	}

	// valid CSR tests
	let csrObject;
	const subjectDN = 'CN=dummy';
	try {
		csrPEM = key3.generateCSR(subjectDN);
		csrObject = asn1.csr.CSRUtil.getInfo(csrPEM);
	} catch (err) {
		t.fail('Failed to generate a CSR: ' + err.stack ? err.stack : err);
	}

	t.equal(asn1.x509.X500Name.onelineToLDAP(csrObject.subject.name), subjectDN,
		'Checking CSR subject matches subject from request');

	t.equal(csrObject.pubkey.obj.pubKeyHex, key3.getPublicKey()._key.pubKeyHex,
		'Checking CSR public key matches requested public key');

	// test X509 generation
	let x509PEM;
	let cert;
	try {
		x509PEM = key3.generateX509Certificate();
		cert = X509.parseCert(x509PEM);
		t.comment(JSON.stringify(cert, '', 2));
		t.equal(cert.subject.commonName, 'self', 'Checking common name set to default');
	} catch (err) {
		t.fail('Failed to generate an X509 Certificate: ' + err.stack ? err.stack : err);
	}

	try {
		x509PEM = key3.generateX509Certificate('testUser');
		cert = X509.parseCert(x509PEM);
		t.comment(JSON.stringify(cert, '', 2));
		t.equal(cert.subject.commonName, 'testUser', 'Checking common name set to "testUser"');
	} catch (err) {
		t.fail('Failed to generate an X509 Certificate: ' + err.stack ? err.stack : err);
	}

	t.throws(
		() => {
			key3.getPublicKey().generateX509Certificate();
		},
		/An X509 certificate cannot be generated from a public key/,
		'Checking that an X509 cannot be generated from a public key'
	);

	t.end();
});
